﻿using ServerCommon.Architecture;
using ServerCommon.Log;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security;
using System.Text;
using System.Threading.Tasks;

using uid_t = System.Int64;

namespace ServerCommon.Net
{
    /// <summary>
    /// 客户端 服务器内部相互通信 buffer大小设置为 2M
    /// </summary>
    public class NetClient : NetNode
    {
        private static LogHelper log = LogHelper.GetLogger(typeof(NetClient));

        public EnumNodeStatusType StatusType => _statusType;
        protected EnumNodeStatusType _statusType;

        public bool IsConnected => _isConnected;
        private bool _isConnected;

        public bool AutoReconnect;
        protected int _isConnecting;

        public ClientSettings ClientConfig => _clientSettings;
        private ClientSettings _clientSettings;

        private Guid _sessionId;

        private long _localNodeId;

        public NetClient() : base()
        {
            _netType = EnumNodeNetType.Client;

            _sessionId = Guid.Empty;
            AutoReconnect = true;
            _isConnected = false;
            _isConnecting = 0;
            _statusType = EnumNodeStatusType.Unknow;
        }

        /// <summary>
        /// 客户端发起连接的NetClient节点
        /// </summary>
        /// <param name="manager"></param>
        /// <param name="serviceType"></param>
        /// <param name="ipStr"></param>
        /// <param name="port"></param>
        /// <param name="IOSAEANum"></param>
        /// <param name="blockSize"></param>
        public void Config(NetNodeManager manager, string ipStr, int port, int IOSAEANum, int blockSize, long targetNodeId, long localNodeId)
        {
            Config(manager);
            _serviceType = EnumNodeServiceType.Client;
            //因为connect用的是New出来的socketasynceventargs
            //所以仅需要一个发送消息和一个接收消息的SAEA
            var ipAddresses = Dns.GetHostAddresses(ipStr);
            IPAddress ipAddress = IPAddress.None;
            foreach (var address in ipAddresses)
            {
                if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork || ipAddress == IPAddress.None)
                {
                    log.InfoFormat("use IPAddress : {0}", address);
                    ipAddress = address;
                }
                log.InfoFormat("ipStr: {0} address: {1}", ipStr, address);
            }
            if (ipAddresses.Length <= 0 || ipAddress == IPAddress.None)
            {
                log.ErrorFormat("can't get ipaddress by ipStr: {0} or ipAddress not contain ipv4.", ipStr);
            }
            ClientSettings setting = new ClientSettings(new IPEndPoint(ipAddress, port), IOSAEANum, blockSize);
            ConfigSetting(setting);
            _addr = setting.RemoteEndPoint;
            _nodeId = targetNodeId; //这里的nodeId是远端进程的id
            _localNodeId = localNodeId;
            manager?.Add(this);
        }

        public void ConfigSetting(ClientSettings settings)
        {
            _clientSettings = settings;
            _bufferManager = new BufferManager((long)_clientSettings.BufferSizeForEachBlock * (_clientSettings.IOSaeaNum + 5), _clientSettings.BufferSizeForEachBlock);
            _ioSaeaPool = new ConcurrentStack<SocketAsyncEventArgs>();
            _bufferManager.InitBuffer();
            CreateIOSAEAPool(_clientSettings.IOSaeaNum);
        }

        /// <summary>
        /// 作为客户端发起连接
        /// </summary>
        /// <returns></returns>
        public EnumErrorCode Connect()
        {
            EnumErrorCode ret;
            try
            {
                Socket clientSocket = new Socket(_clientSettings.RemoteEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                SocketAsyncEventArgs connectEventArgs = CreateConnectEventArgs();
                connectEventArgs.AcceptSocket = clientSocket;
                connectEventArgs.RemoteEndPoint = _clientSettings.RemoteEndPoint;

                bool willRaise = clientSocket.ConnectAsync(connectEventArgs);
                if (!willRaise)
                {
                    ProcessConnect(connectEventArgs);
                }
            }
            catch (SocketException ex)
            {
                log.ErrorFormat("Connect failed SocketException : {0} , ErrorCode: {1} ", ex.Message, ex.ErrorCode);
            }
            catch (SecurityException ex)
            {
                log.ErrorFormat("Connect failed SecurityException : {0} ", ex.Message);
            }
            catch (Exception ex)
            {
                log.ErrorFormat("Connect failed Exception : {0} ", ex.Message);
            }
            finally
            {
                ret = EnumErrorCode.ConnectFail;
            }
            return ret;
        }

        private void Connect_Completed(object sender, SocketAsyncEventArgs connectEventArgs)
        {
            if (connectEventArgs.SocketError != SocketError.Success)
            {
                _manager.PushIoEvent(new ConnectionEventArgs()
                {
                    EventType = EnumConnectionEventType.Connect_Error,
                    Session = null,
                    NodeId = _nodeId,
                });

                var socket = connectEventArgs.AcceptSocket;
                string addr = string.Empty;
                if (connectEventArgs.SocketError == SocketError.ConnectionRefused)
                {
                    log.WarnFormat("socket connect fail, sock:{0}, error:{1}", addr, connectEventArgs.SocketError.ToString());
                }
                else
                {
                    log.ErrorFormat("socket connect fail, sock:{0}, error:{1}", addr, connectEventArgs.SocketError.ToString());
                }
                if (socket != null)
                {
                    socket.Close();
                }

                _isConnected = false;
                ResetConnectFlag();
                if (AutoReconnect)
                {
                    NetNodeReconnect.Instance.StartReconnect(this);
                }
                else
                {
                    Manager?.Del(this);
                }

                return;
            }

            switch (connectEventArgs.LastOperation)
            {
                case SocketAsyncOperation.Connect:
                    ProcessConnect(connectEventArgs);
                    break;
                default:
                    {
                        log.ErrorFormat("Connect_Completed Error: {0}", connectEventArgs.LastOperation);
                    }
                    break;
            }
        }

        private void ProcessConnect(SocketAsyncEventArgs connectEventArgs)
        {
            if (connectEventArgs.SocketError != SocketError.Success)
            {
                _manager.PushIoEvent(new ConnectionEventArgs()
                {
                    EventType = EnumConnectionEventType.Connect_Error,
                    Session = null,
                    NodeId = _nodeId,
                });

            }
            else
            {
                if (!_ioSaeaPool.TryPop(out var ioEventArgs))
                {
                    log.Error("No available SocketAsyncEventArgs ");
                    return;
                }
                ioEventArgs.AcceptSocket = connectEventArgs.AcceptSocket;
                connectEventArgs.AcceptSocket = null;

                DataToken dataToken = ioEventArgs.UserToken as DataToken;

                ioEventArgs.SetBuffer(dataToken.BufferOffset, _bufferManager.BufferSizeForEachBlock);

                var exsocketConfig = NetSessionConfig.GetConfigByNodeType(_serviceType);
                NetSession session = new NetSession(ioEventArgs.AcceptSocket, exsocketConfig, Guid.NewGuid(), _serviceType);
                session._remoteNodeId = _nodeId;
                session._localNodeId = _localNodeId;
                _sessionId = session.HashCode;
                dictSession.Add(_sessionId, session);
                dictIdSession.Add(_nodeId, session);
                dataToken.Session = session;

                _isConnected = true;

                ResetConnectFlag();
                NetNodeReconnect.Instance.StopReconnect(this);

                _manager.PushIoEvent(new ConnectionEventArgs()
                {
                    EventType = EnumConnectionEventType.Connect,
                    Session = session,
                    NodeId = _nodeId,
                });

                PostReceive(ioEventArgs);
            }
        }

        private SocketAsyncEventArgs CreateConnectEventArgs()
        {
            SocketAsyncEventArgs connectEventArgs = new SocketAsyncEventArgs();
            connectEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(Connect_Completed);
            return connectEventArgs;
        }

        public override void Start()
        {
            base.Start();
            if (TrySetConnectFlag())
            {
                CloseConnection();
                Connect();
                log.InfoFormat("start connect service. type:{0}, ip:{1}", _serviceType.ToString(), ClientConfig.RemoteEndPoint);
            }
        }

        public override void Stop()
        {
            base.Stop();
            NetNodeReconnect.Instance.StopReconnect(this);
            AutoReconnect = false;
            CloseConnection();
        }

        public virtual EnumSendProtocolReturn SendProtocol(Pb.IProtocol protocol)
        {
            if(!dictSession.ContainsKey(_sessionId))
            {
                log.ErrorFormat("NetClient.SendProtocol session invalid. localNodeId:{0}, remoteNodeId:{1}", _localNodeId, _nodeId);
                return EnumSendProtocolReturn.Invalid_Session;
            }

            var buffer = protocol.Serialize();
            if (buffer.Length > 64 * 1024)
            {
                log.WarnFormat("NetClient.SendProtocol. proto:{0} len:{1}", protocol.GetType(), buffer.Length);
            }
            SendMessageAsync(dictSession[_sessionId], buffer);
            return EnumSendProtocolReturn.OK;
        }

        //先写timestamp,再写sessionId，最后data
        private EnumSendProtocolReturn SendProtocolClientSync(Pb.IProtocol protocol)
        {
            //var syn = protocol as S2SClientSessionSendDataSyn;

            ////buffer的结构:
            ////buffer长度4 + protocol协议ID4 + 时间戳8 + count4 + sessionIds + data
            //var buffer = new byte[4 + 4 + 8 + 4 + sizeof(uid_t) * syn.SessionId.Count + syn.Data.Length];
            //var byteDataLen = BitConverter.GetBytes(buffer.Length - 4);
            //Buffer.BlockCopy(byteDataLen, 0, buffer, 0, byteDataLen.Length);
            //var pos = byteDataLen.Length;
            ////
            //byteDataLen = BitConverter.GetBytes(protocol.ProtocolId);
            //Buffer.BlockCopy(byteDataLen, 0, buffer, pos, byteDataLen.Length);
            //pos += byteDataLen.Length;

            //byteDataLen = BitConverter.GetBytes(syn.Timestamp);
            //Buffer.BlockCopy(byteDataLen, 0, buffer, pos, byteDataLen.Length);
            //pos += byteDataLen.Length;

            //byteDataLen = BitConverter.GetBytes(syn.SessionId.Count);
            //Buffer.BlockCopy(byteDataLen, 0, buffer, pos, byteDataLen.Length);
            //pos += byteDataLen.Length;

            //foreach (var sessionId in syn.SessionId)
            //{
            //    byteDataLen = BitConverter.GetBytes(sessionId);
            //    if (byteDataLen.Length != sizeof(uid_t))
            //    {
            //        log.Warn($"SendProtocolClientSync. sessionId:{sessionId} length:{byteDataLen.Length} != {sizeof(uid_t)}");
            //        continue;
            //    }

            //    Buffer.BlockCopy(byteDataLen, 0, buffer, pos, byteDataLen.Length);
            //    pos += byteDataLen.Length;
            //}

            //Buffer.BlockCopy(syn.Data.ToByteArray(), 0, buffer, pos, syn.Data.Length);
            //pos += syn.Data.Length;

            //if (buffer.Length > 64 * 1024)
            //{
            //    log.WarnFormat("SendProtocolClientSync. proto:{0} len:{1}",
            //        protocol.GetType(), buffer.Length);
            //}

            //SendMessageAsync(_session, buffer);
            return EnumSendProtocolReturn.OK;
        }

        //断线重连的时候使用，检测当前是否处于连接的状态
        public bool TrySetConnectFlag()
        {
            return Interlocked.CompareExchange(ref _isConnecting, 1, 0) == 0;
        }

        public void ResetConnectFlag()
        {
            Interlocked.Exchange(ref _isConnecting, 0);
        }

        public bool IsConnecting()
        {
            return Interlocked.Equals(_isConnecting, 1);
        }

        public virtual void CloseConnection()
        {
            if (dictSession.ContainsKey(_sessionId))
            {
                CloseClientSocket(dictSession[_sessionId]);
                OnDisConnectSession(dictSession[_sessionId]);
            }
        }

        protected override void OnDisConnectSession(NetSession session)
        {
            base.OnDisConnectSession(session);
            _sessionId = Guid.Empty;
        }

        public override StringBuilder ToLog()
        {
            return base.ToLog().AppendFormat("StatusType:{0},", StatusType);
        }
    }
}
